/*
NPlot - A charting library for .NET

Axis.cs
Copyright (C) 2003
Matt Howlett

Redistribution and use of NPlot or parts there-of in source and
binary forms, with or without modification, are permitted provided
that the following conditions are met:

1. Re-distributions in source form must retain at the head of each
   source file the above copyright notice, this list of conditions
   and the following disclaimer.

2. Any product ("the product") that makes use NPlot or parts 
   there-of must either:
  
    (a) allow any user of the product to obtain a complete machine-
        readable copy of the corresponding source code for the 
        product and the version of NPlot used for a charge no more
        than your cost of physically performing source distribution,
	    on a medium customarily used for software interchange, or:

    (b) reproduce the following text in the documentation, about 
        box or other materials intended to be read by human users
        of the product that is provided to every human user of the
        product: 
   
              "This product includes software developed as 
              part of the NPlot library project available 
              from: http://www.nplot.com/" 

        The words "This product" may optionally be replace with 
        the actual name of the product.

------------------------------------------------------------------------

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

using System.Drawing.Drawing2D;
using System.Drawing;
using System;
using System.Collections;

namespace NPlot
{

	/// <summary>
	/// Encapsulates functionality common to all axis classes. All specific
	/// axis classes derive from Axis. Axis can be used as a concrete class
	/// itself - it is an Axis without any embilishments [tick marks or tick
	/// mark labels].<br></br><br></br>
	/// This class encapsulates no physical information about where the axes
	/// are drawn. 
	/// </summary>
	public class Axis : System.ICloneable
	{
	
		/// <summary>
		/// If true, tick marks will cross the axis, with their centre on the axis line.
		/// If false, tick marks will be drawn as a line with origin starting on the axis line.
		/// </summary>
		public bool TicksCrossAxis
		{
			get
			{
				return ticksCrossAxis_;
			}
			set
			{
				ticksCrossAxis_ = value;
			}
		}
		bool ticksCrossAxis_ = false;


		/// <summary>
		/// The maximum world extent of the axis. Note that it is sensical if 
		/// WorldMax is less than WorldMin - the axis would just be descending
		/// not ascending. Currently Axes won't display properly if you do 
		/// this - use the Axis.Reversed property instead to achieve the same
		/// result.
		/// 
        /// Setting this raises the WorldMinChanged event and the WorldExtentsChanged event.
        /// </summary>
		public virtual double WorldMax
		{
			get
			{
				return worldMax_;
			}
			set
			{
				this.worldMax_ = value;
                /*
                if (this.WorldExtentsChanged != null)
                    this.WorldExtentsChanged(this, new WorldValueChangedArgs(worldMax_, WorldValueChangedArgs.MinMaxType.Max));
                if (this.WorldMaxChanged != null)
                    this.WorldMaxChanged(this, new WorldValueChangedArgs(worldMax_, WorldValueChangedArgs.MinMaxType.Max));
                */
            }
		}
		private double worldMax_;
		

		/// <summary>
		/// The minumum world extent of the axis. Note that it is sensical if 
		/// WorldMax is less than WorldMin - the axis would just be descending
		/// not ascending. Currently Axes won't display properly if you do 
		/// this - use the Axis.Reversed property instead to achieve the same
		/// result.
		/// 
		/// Setting this raises the WorldMinChanged event and the WorldExtentsChanged event.
		/// </summary>
		public virtual double WorldMin
		{
			get
			{
				return this.worldMin_;
			}
			set
			{
				this.worldMin_ = value;
                /*
                if (this.WorldExtentsChanged != null)
                    this.WorldExtentsChanged( this, new WorldValueChangedArgs( worldMin_, WorldValueChangedArgs.MinMaxType.Min) );
                if (this.WorldMinChanged != null)
                    this.WorldMinChanged( this, new WorldValueChangedArgs(worldMin_, WorldValueChangedArgs.MinMaxType.Min) );
                */
            }
		}
		private double worldMin_;


		/// <summary>
		/// Length (in pixels) of a large tick. <b>Not</b> the distance 
		/// between large ticks. The length of the tick itself.
		/// </summary>
		public int LargeTickSize
		{
			get
			{
				return largeTickSize_;
			}
			set
			{
				largeTickSize_ = value;
			}
		}
		private int largeTickSize_;


		/// <summary>
		/// Length (in pixels) of the small ticks.
		/// </summary>
		public int SmallTickSize
		{
			get
			{
				return smallTickSize_;
			}
			set
			{
				smallTickSize_ = value;
			}
		}
		private int smallTickSize_;


		/// <summary>
		/// The Axis Label
		/// </summary>
		public string Label
		{
			get
			{
				return label_;
			}
			set
			{
				label_ = value;
			}
		}
		private string label_;


		/// <summary>
		/// If true, text associated with tick marks will be drawn on the other side of the
		/// axis line [next to the axis]. If false, tick mark text will be drawn at the end
		/// of the tick mark [on the same of the axis line as the tick].
		/// </summary>
		public bool TickTextNextToAxis
		{
			get
			{
				return tickTextNextToAxis_;
			}
			set
			{
				tickTextNextToAxis_ = value;
			}
		}
		bool tickTextNextToAxis_;


		/// <summary>
		/// If set to true, the axis is hidden. That is, the axis line, ticks, tick 
		/// labels and axis label will not be drawn. 
		/// </summary>
		public bool Hidden
		{
			get
			{
				return hidden_;
			}
			set
			{
				hidden_ = value;
			}
		}
		private bool hidden_;


		/// <summary>
		/// If set true, the axis will behave as though the WorldMin and WorldMax values
		/// have been swapped.
		/// </summary>
		public bool Reversed
		{
			get
			{
				return reversed_;
			}
			set
			{
				reversed_ = value;
			}
		}
		private bool reversed_;


		/// <summary>
		/// If true, no text will be drawn next to any axis tick marks.
		/// </summary>
		public bool HideTickText
		{
			get
			{
				return hideTickText_;
			}
			set
			{
				hideTickText_ = value;
			}
		}
		private bool hideTickText_;


		/// <summary>
		/// This font is used for the drawing of text next to the axis tick marks.
		/// </summary>
		public Font TickTextFont
		{
			get
			{
				return this.tickTextFont_;
			}
			set
			{
				this.tickTextFont_ = value;
				UpdateScale();
			}
		}
		private Font tickTextFont_;
		private Font tickTextFontScaled_;


		/// <summary>
		/// This font is used to draw the axis label.
		/// </summary>
		public Font LabelFont
		{
			get
			{
				return labelFont_;
			}
			set
			{
				labelFont_ = value;
				UpdateScale();
			}
		}
		private Font labelFont_;
		private Font labelFontScaled_;


		/// <summary>
		/// Specifies the format used for drawing tick labels. See 
		/// StringBuilder.AppendFormat for a description of this 
		/// string.
		/// </summary>
		public string NumberFormat
		{
			get
			{
				return numberFormat_;
			}
			set
			{
				numberFormat_ = value;
			}
		}
		private string numberFormat_;


		/// <summary>
		/// If LargeTickStep isn't specified, then this will be calculated 
		/// automatically. The calculated value will not be less than this
		/// amount.
		/// </summary>
		public int MinPhysicalLargeTickStep
		{
			get
			{
				return minPhysicalLargeTickStep_;
			}
			set
			{
				minPhysicalLargeTickStep_ = value;
			}
		}
		private int minPhysicalLargeTickStep_ = 30;


		/// <summary>
		/// The color of the pen used to draw the ticks and the axis line.
		/// </summary>
		public System.Drawing.Color AxisColor
		{
			get
			{
				return linePen_.Color;
			}
			set
			{
				linePen_ = new Pen( (Color)value );
			}
		}


		/// <summary>
		/// The pen used to draw the ticks and the axis line.
		/// </summary>
		public System.Drawing.Pen AxisPen
		{
			get
			{
				return linePen_;
			}
			set
			{
				linePen_ = value;
			}
		}
		private System.Drawing.Pen linePen_;


		/// <summary>
		/// If true, automated tick placement will be independent of the physical
		/// extent of the axis. Tick placement will look good for charts of typical
		/// size (say physical dimensions of 640x480). If you want to produce the
		/// same chart on two graphics surfaces of different sizes [eg Windows.Forms
		/// control and printer], then you will want to set this property to true.
		/// If false [default], the number of ticks and their placement will be 
		/// optimally calculated to look the best for the given axis extent. This 
		/// is very useful if you are creating a cart with particularly small or
		/// large physical dimensions.
		/// </summary>
		public bool TicksIndependentOfPhysicalExtent
		{
			get
			{
				return ticksIndependentOfPhysicalExtent_;
			}
			set
			{
				ticksIndependentOfPhysicalExtent_ = value;
			}
		}
		private bool ticksIndependentOfPhysicalExtent_ = false;


		/// <summary>
		/// If true label is flipped about the text center line parallel to the text.
		/// </summary>
		public bool FlipTicksLabel
		{
			get 
			{
				return flipTicksLabel_; 
			}
			set 
			{ 
				flipTicksLabel_ = value; 
			}
		}
 		private bool flipTicksLabel_ = false;


		/// <summary>
		/// Angle to draw ticks at (measured anti-clockwise from axis direction).
		/// </summary>
		public float TicksAngle
		{
			get
			{
				return ticksAngle_;
			}
			set
			{
				ticksAngle_ = value;
			}
		}
		private float ticksAngle_ = (float)Math.PI / 2.0f;


		/// <summary>
		/// Angle to draw large tick labels at (clockwise from horizontal). Note: 
		/// this is currently only implemented well for the lower x-axis. 
		/// </summary>
		public float TicksLabelAngle
		{
			get
			{
				return ticksLabelAngle_;
			}
			set
			{
				ticksLabelAngle_ = value;
			}
		}
		private float ticksLabelAngle_ = 0.0f;


		/// <summary>
		/// The color of the brush used to draw the axis label.
		/// </summary>
		public Color LabelColor
		{
			set
			{
				labelBrush_ = new SolidBrush( value );
			}
		}
		
		
		/// <summary>
		/// The brush used to draw the axis label.
		/// </summary>
		public Brush LabelBrush
		{
			get
			{
				return labelBrush_;
			}
			set
			{
				labelBrush_ = value;
			}
		}
		private Brush labelBrush_;

		
		/// <summary>
		/// The color of the brush used to draw the axis tick labels.
		/// </summary>
		public Color TickTextColor
		{
			set
			{
				tickTextBrush_ = new SolidBrush( value );
			}
		}


		/// <summary>
		/// The brush used to draw the tick text.
		/// </summary>
		public Brush TickTextBrush
		{
			get
			{
				return tickTextBrush_;
			}
			set
			{
				tickTextBrush_ = value;
			}
		}
		private Brush tickTextBrush_;


		/// <summary>
		/// If true, label and tick text will be scaled to match size
		/// of PlotSurface2D. If false, they won't be.
		/// </summary>
		/// <remarks>Could also be argued this belongs in PlotSurface2D</remarks>
		public bool AutoScaleText
		{
			get
			{
				return autoScaleText_;
			}
			set
			{
				autoScaleText_ = value;
			}
		}
		private bool autoScaleText_;


		/// <summary>
		/// If true, tick lengths will be scaled to match size
		/// of PlotSurface2D. If false, they won't be.
		/// </summary>
		/// <remarks>Could also be argued this belongs in PlotSurface2D</remarks>
		public bool AutoScaleTicks
		{
			get
			{
				return autoScaleTicks_;
			}
			set
			{
				autoScaleTicks_ = value;
			}
		}
		private bool autoScaleTicks_;


		/// <summary>
		/// Deep copy of Axis.
		/// </summary>
		/// <remarks>
		/// This method includes a check that guards against derived classes forgetting
		/// to implement their own Clone method. If Clone is called on a object derived
		/// from Axis, and the Clone method hasn't been overridden by that object, then
		/// the test this.GetType == typeof(Axis) will fail.
		/// </remarks>
		/// <returns>A copy of the Axis Class</returns>
		public virtual object Clone()
		{
			// ensure that this isn't being called on a derived type. If that is the case
			// then the derived type didn't override this method as it should have.
			if (this.GetType() != typeof(Axis))
			{
				throw new NPlotException( "Clone not defined in derived type." );
			}
			
			Axis a = new Axis();
			DoClone( this, a );
			return a;
		}


		/// <summary>
		/// Helper method for Clone. Does all the copying - can be called by derived
		/// types so they don't need to implement this part of the copying themselves.
		/// also useful in constructor of derived types that takes Axis class.
		/// </summary>
		protected static void DoClone( Axis b, Axis a )
		{

			// value items
			a.autoScaleText_ = b.autoScaleText_;
			a.autoScaleTicks_ = b.autoScaleTicks_;
			a.worldMax_ = b.worldMax_;
			a.worldMin_ = b.worldMin_;
			a.tickTextNextToAxis_ = b.tickTextNextToAxis_;
			a.hidden_ = b.hidden_;
			a.hideTickText_ = b.hideTickText_;
			a.reversed_ = b.reversed_;
			a.ticksAngle_ = b.ticksAngle_;
			a.ticksLabelAngle_ = b.ticksLabelAngle_;
			a.minPhysicalLargeTickStep_ = b.minPhysicalLargeTickStep_;
			a.ticksIndependentOfPhysicalExtent_ = b.ticksIndependentOfPhysicalExtent_;
			a.largeTickSize_ = b.largeTickSize_;
			a.smallTickSize_ = b.smallTickSize_;
			a.ticksCrossAxis_ = b.ticksCrossAxis_;
			a.labelOffset_ = b.labelOffset_;
            a.labelOffsetAbsolute_ = b.labelOffsetAbsolute_;
            a.labelOffsetScaled_ = b.labelOffsetScaled_;

			// reference items.
			a.tickTextFont_ = (Font)b.tickTextFont_.Clone();
			a.label_ = (string)b.label_.Clone();
			if (b.numberFormat_ != null) 
			{
				a.numberFormat_ = (string)b.numberFormat_.Clone();
			}
			else
			{
				a.numberFormat_ = null;
			}

			a.labelFont_ = (Font)b.labelFont_.Clone();
			a.linePen_ = (Pen)b.linePen_.Clone();
			a.tickTextBrush_ = (Brush)b.tickTextBrush_.Clone();
			a.labelBrush_ = (Brush)b.labelBrush_.Clone();

			a.FontScale = b.FontScale;
			a.TickScale = b.TickScale;

		}



		/// <summary>
		/// Helper function for constructors.
		/// Do initialization here so that Clear() method is handled properly
		/// </summary>
		private void Init()
		{
			this.worldMax_ = double.NaN;
			this.worldMin_ = double.NaN;
			this.Hidden = false;
			this.SmallTickSize = 2;
			this.LargeTickSize = 6;
			this.FontScale = 1.0f;
			this.TickScale = 1.0f;
			this.AutoScaleTicks = false;
			this.AutoScaleText = false;
			this.TickTextNextToAxis = true;
			this.HideTickText = false;
			this.TicksCrossAxis = false;
			this.LabelOffset = 0.0f;
            this.LabelOffsetAbsolute = false;
            this.LabelOffsetScaled = true;

			this.Label = "" ;
			this.NumberFormat = null;
			this.Reversed = false;

			FontFamily fontFamily = new FontFamily( "Arial" );
			this.TickTextFont = new Font( fontFamily, 10, FontStyle.Regular, GraphicsUnit.Pixel );
			this.LabelFont = new Font( fontFamily, 12, FontStyle.Regular, GraphicsUnit.Pixel );
			this.LabelColor = System.Drawing.Color.Black;
			this.TickTextColor = System.Drawing.Color.Black;
			this.linePen_ = new Pen( System.Drawing.Color.Black );
			this.linePen_.Width = 1.0f;
			this.FontScale = 1.0f;

			// saves constructing these in draw method.
			drawFormat_ = new StringFormat();
			drawFormat_.Alignment = StringAlignment.Center;
		}

		StringFormat drawFormat_;


		/// <summary>
		/// Default constructor
		/// </summary>
		public Axis( )
		{
			this.Init();
		}


		/// <summary>
		/// Constructor that takes only world min and max values.
		/// </summary>
		/// <param name="worldMin">The minimum world coordinate.</param>
		/// <param name="worldMax">The maximum world coordinate.</param>
		public Axis( double worldMin, double worldMax )
		{
			this.Init();
			this.WorldMin = worldMin;
			this.WorldMax = worldMax;
		}


		/// <summary>
		/// Copy constructor.
		/// </summary>
		/// <param name="a">The Axis to clone.</param>
		public Axis( Axis a )
		{
			Axis.DoClone( a, this );
		}


		/// <summary>
		/// Determines whether a world value is outside range WorldMin -> WorldMax
		/// </summary>
		/// <param name="coord">the world value to test</param>
		/// <returns>true if outside limits, false otherwise</returns>
		public bool OutOfRange( double coord )
		{
			if (double.IsNaN(WorldMin) || double.IsNaN(WorldMax))
			{
				throw new NPlotException( "world min / max not set" );
			}

			if (coord > this.WorldMax || coord < this.WorldMin)
			{
				return true;
			}
			else
			{
				return false;
			}
		}


		/// <summary>
		/// Sets the world extent of the current axis to be just large enough
		/// to encompas the current world extent of the axis, and the world
		/// extent of the passed in axis
		/// </summary>
		/// <param name="a">The other Axis instance.</param>
		public void LUB( Axis a )
		{
			if (a == null)
			{
				return;
			}

			// mins
			if (!double.IsNaN(a.worldMin_))
			{
				if (double.IsNaN(worldMin_))
				{
					WorldMin = a.WorldMin;
				}
				else
				{
					if (a.WorldMin < WorldMin)
					{
						WorldMin = a.WorldMin;
					}
				}
			}

			// maxs.
			if (!double.IsNaN(a.worldMax_))
			{
				if (double.IsNaN(worldMax_))
				{
					WorldMax = a.WorldMax;
				}
				else
				{
					if (a.WorldMax > WorldMax)
					{
						WorldMax = a.WorldMax;
					}
				}
			}
		}


		/// <summary>
		/// World to physical coordinate transform.
		/// </summary>
		/// <param name="coord">The coordinate value to transform.</param>
		/// <param name="physicalMin">The physical position corresponding to the world minimum of the axis.</param>
		/// <param name="physicalMax">The physical position corresponding to the world maximum of the axis.</param>
		/// <param name="clip">if false, then physical value may extend outside worldMin / worldMax. If true, the physical value returned will be clipped to physicalMin or physicalMax if it lies outside this range.</param>
		/// <returns>The transformed coordinates.</returns>
		/// <remarks>Not sure how much time is spent in this often called function. If it's lots, then
		/// worth optimizing (there is scope to do so).</remarks>
		public virtual PointF WorldToPhysical( 
			double coord, 
			PointF physicalMin, 
			PointF physicalMax, 
			bool clip )
		{

			// (1) account for reversed axis. Could be tricky and move
			// this out, but would be a little messy.

			PointF _physicalMin;
			PointF _physicalMax;

			if ( this.Reversed )
			{
				_physicalMin = physicalMax;
				_physicalMax = physicalMin;
			}
			else
			{
				_physicalMin = physicalMin;
				_physicalMax = physicalMax;
			}


			// (2) if want clipped value, return extrema if outside range.
			
			if ( clip )
			{
				if ( WorldMin < WorldMax )
				{
					if ( coord > WorldMax )
					{
						return _physicalMax;
					}
					if ( coord < WorldMin )
					{
						return _physicalMin;
					}
				}
				else
				{
					if ( coord < WorldMax )
					{
						return _physicalMax;
					}
					if ( coord > WorldMin )
					{
						return _physicalMin;
					}
				}
			}


			// (3) we are inside range or don't want to clip.

			double range = WorldMax - WorldMin;
			double prop = (double)((coord - WorldMin) / range);

			// Force clipping at bounding box largeClip times that of real bounding box 
			// anyway. This is effectively at infinity.
			const double largeClip = 100.0;
			if (prop > largeClip && clip)
				prop = largeClip;

			if (prop < -largeClip && clip)
				prop = -largeClip;

			if (range == 0)
			{
				if (coord >= WorldMin)
					prop = largeClip;

				if (coord < WorldMin)
					prop = -largeClip;
			}

			// calculate the physical coordinate.
			PointF offset = new PointF( 
				(float)(prop * (_physicalMax.X - _physicalMin.X)),
				(float)(prop * (_physicalMax.Y - _physicalMin.Y)) );

			return new PointF( (int)(_physicalMin.X + offset.X), (int)(_physicalMin.Y + offset.Y) );
		}


		/// <summary>
		/// Return the world coordinate of the projection of the point p onto
		/// the axis.
		/// </summary>
		/// <param name="p">The point to project onto the axis</param>
		/// <param name="physicalMin">The physical position corresponding to the world minimum of the axis.</param>
		/// <param name="physicalMax">The physical position corresponding to the world maximum of the axis.</param>
		/// <param name="clip">If true, the world value will be clipped to WorldMin or WorldMax as appropriate if it lies outside this range.</param>
		/// <returns>The world value corresponding to the projection of the point p onto the axis.</returns>
		public virtual double PhysicalToWorld( 
			PointF p, 
			PointF physicalMin, 
			PointF physicalMax,
			bool clip )
		{

			// (1) account for reversed axis. Could be tricky and move
			// this out, but would be a little messy.

			PointF _physicalMin;
			PointF _physicalMax;

			if ( this.Reversed )
			{
				_physicalMin = physicalMax;
				_physicalMax = physicalMin;
			}
			else
			{
				_physicalMin = physicalMin;
				_physicalMax = physicalMax;
			}

			// normalised axis dir vector
			float axis_X = _physicalMax.X - _physicalMin.X;
			float axis_Y = _physicalMax.Y - _physicalMin.Y;
			float len = (float)Math.Sqrt( axis_X * axis_X + axis_Y * axis_Y );
			axis_X /= len;
			axis_Y /= len;

			// point relative to axis physical minimum.
			PointF posRel = new PointF( p.X - _physicalMin.X, p.Y - _physicalMin.Y );

			// dist of point projection on axis, normalised.
			float prop = ( axis_X * posRel.X + axis_Y * posRel.Y ) / len;

			double world = prop * (this.WorldMax - this.WorldMin) + this.WorldMin;

			// if want clipped value, return extrema if outside range.
			if (clip)
			{
				world = Math.Max( world, this.WorldMin );
				world = Math.Min( world, this.WorldMax );
			}

			return world;
		}


		/// <summary>
		/// Draw the Axis Label
		/// </summary>
		/// <param name="g">The GDI+ drawing surface on which to draw.</param>
		/// <param name="offset">offset from axis. Should be calculated so as to make sure axis label misses tick labels.</param>
		/// <param name="axisPhysicalMin">The physical position corresponding to the world minimum of the axis.</param>
		/// <param name="axisPhysicalMax">The physical position corresponding to the world maximum of the axis.</param>
		/// <returns>boxed Rectangle indicating bounding box of label. null if no label printed.</returns>
		public object DrawLabel( 
			Graphics g, 
			Point offset, 
			Point axisPhysicalMin, 
			Point axisPhysicalMax )
		{

			if ( Label != "" )
			{
	
				// first calculate any extra offset for axis label spacing.
				float extraOffsetAmount = this.LabelOffset;
				extraOffsetAmount += 2.0f; // empirically determed - text was too close to axis before this.
				
				if (this.AutoScaleText)
				{
					if (this.LabelOffsetScaled)
					{
						extraOffsetAmount *= this.FontScale;
					}
				}

				// now extend offset.
				float offsetLength = (float)Math.Sqrt( offset.X*offset.X + offset.Y*offset.Y );
				if (offsetLength > 0.01)
				{
					float x_component = offset.X / offsetLength;
					float y_component = offset.Y / offsetLength;

					x_component *= extraOffsetAmount;
					y_component *= extraOffsetAmount;

                    if (this.LabelOffsetAbsolute)
                    {
                        offset.X = (int)x_component;
                        offset.Y = (int)y_component;
                    }
                    else
                    {
                        offset.X += (int)x_component;
                        offset.Y += (int)y_component;
                    }
                }
				
				// determine angle of axis in degrees
				double theta = Math.Atan2(
					axisPhysicalMax.Y - axisPhysicalMin.Y,
					axisPhysicalMax.X - axisPhysicalMin.X );
				theta = theta * 180.0f / Math.PI;

				PointF average = new PointF(
					(axisPhysicalMax.X + axisPhysicalMin.X)/2.0f,
					(axisPhysicalMax.Y + axisPhysicalMin.Y)/2.0f );

				g.TranslateTransform( offset.X , offset.Y );	// this is done last.
				g.TranslateTransform( average.X, average.Y );
				g.RotateTransform( (float)theta );				// this is done first.

				SizeF labelSize = g.MeasureString( Label, labelFontScaled_);

				//bounding box for label centered around zero.
				RectangleF drawRect = new RectangleF( 
					-labelSize.Width/2.0f,
					-labelSize.Height/2.0f,
					labelSize.Width,
					labelSize.Height );
				
				g.DrawString( 
					Label, 
					labelFontScaled_,
					labelBrush_, 
					drawRect,
					drawFormat_ );

				// now work out physical bounds of label. 
				Matrix m = g.Transform;
				PointF[] recPoints = new PointF[2];
				recPoints[0] = new PointF( -labelSize.Width/2.0f, -labelSize.Height/2.0f );
				recPoints[1] = new PointF( labelSize.Width/2.0f, labelSize.Height/2.0f );
				m.TransformPoints( recPoints );

				int x1 = (int)Math.Min( recPoints[0].X, recPoints[1].X );
				int x2 = (int)Math.Max( recPoints[0].X, recPoints[1].X );
				int y1 = (int)Math.Min( recPoints[0].Y, recPoints[1].Y );
				int y2 = (int)Math.Max( recPoints[0].Y, recPoints[1].Y );

				g.ResetTransform();

				// and return label bounding box.
				return new Rectangle( x1, y1, (x2-x1), (y2-y1) );
			}

			return null;
		}


		/// <summary>
		/// Draw a tick on the axis.
		/// </summary>
		/// <param name="g">The graphics surface on which to draw.</param>
		/// <param name="w">The tick position in world coordinates.</param>
		/// <param name="size">The size of the tick (in pixels)</param>
		/// <param name="text">The text associated with the tick</param>
		/// <param name="textOffset">The Offset to draw from the auto calculated position</param>
		/// <param name="axisPhysMin">The minimum physical extent of the axis</param>
		/// <param name="axisPhysMax">The maximum physical extent of the axis</param>
		/// <param name="boundingBox">out: The bounding rectangle for the tick and tickLabel drawn</param>
		/// <param name="labelOffset">out: offset from the axies required for axis label</param>
		public virtual void DrawTick( 
			Graphics g, 
			double w,
			float size,
			string text,
			Point textOffset,
			Point axisPhysMin,
			Point axisPhysMax,
			out Point labelOffset,
			out Rectangle boundingBox )
		{

			// determine physical location where tick touches axis. 
			PointF tickStart = WorldToPhysical( w, axisPhysMin, axisPhysMax, true );

			// determine offset from start point.
			PointF axisDir = Utils.UnitVector( axisPhysMin, axisPhysMax );

			// rotate axis dir clockwise by angle radians to get tick direction.
			float x1 = (float)(Math.Cos( -this.TicksAngle ) * axisDir.X + Math.Sin( -this.TicksAngle ) * axisDir.Y);
			float y1 = (float)(-Math.Sin( -this.TicksAngle ) * axisDir.X + Math.Cos( -this.TicksAngle ) * axisDir.Y);

			// now get the scaled tick vector.
			PointF tickVector = new PointF( this.TickScale * size * x1, this.TickScale * size * y1 );

			if (this.TicksCrossAxis)
			{
				tickStart = new PointF(
					tickStart.X - tickVector.X / 2.0f,
					tickStart.Y - tickVector.Y / 2.0f );
			}

			// and the end point [point off axis] of tick mark.
			PointF tickEnd = new PointF( tickStart.X + tickVector.X, tickStart.Y + tickVector.Y );

			// and draw it!
			if (g != null)
				g.DrawLine( this.linePen_, (int)tickStart.X, (int)tickStart.Y, (int)tickEnd.X, (int)tickEnd.Y );
			// note: casting to int for tick positions was necessary to ensure ticks drawn where we wanted
			// them. Not sure of the reason.

			// calculate bounds of tick.
			int minX = (int)Math.Min( tickStart.X, tickEnd.X );
			int minY = (int)Math.Min( tickStart.Y, tickEnd.Y );
			int maxX = (int)Math.Max( tickStart.X, tickEnd.X );
			int maxY = (int)Math.Max( tickStart.Y, tickEnd.Y );
			boundingBox = new Rectangle( minX, minY, maxX-minX, maxY-minY );
			
			// by default, label offset from axis is 0. TODO: revise this.
			labelOffset = new Point( 
				-(int)tickVector.X, 
				-(int)tickVector.Y );

			// ------------------------

			// now draw associated text.

			// **** TODO ****
			// The following code needs revising. A few things are hard coded when
			// they should not be. Also, angled tick text currently just works for
			// the bottom x-axis. Also, it's a bit hacky.

			if (text != "" && !HideTickText && g != null )
			{
				SizeF textSize = g.MeasureString( text, tickTextFontScaled_ );

				// determine the center point of the tick text.
				float textCenterX;
				float textCenterY;

				// if text is at pointy end of tick.
				if (!this.TickTextNextToAxis)
				{
					// offset due to tick.
					textCenterX = tickStart.X + tickVector.X*1.2f;
					textCenterY = tickStart.Y + tickVector.Y*1.2f;

					// offset due to text box size.
					textCenterX += 0.5f * x1 * textSize.Width;
					textCenterY += 0.5f * y1 * textSize.Height;
				}
					// else it's next to the axis.
				else
				{
					// start location.
					textCenterX = tickStart.X;
					textCenterY = tickStart.Y;

					// offset due to text box size.
					textCenterX -= 0.5f * x1 * textSize.Width;
					textCenterY -= 0.5f * y1 * textSize.Height;

					// bring text away from the axis a little bit.
					textCenterX -= x1*(2.0f+FontScale);
					textCenterY -= y1*(2.0f+FontScale);
				}

				// If tick text is angled.. 
				if (this.TicksLabelAngle != 0.0f)
				{

					// determine the point we want to rotate text about.
					
					PointF textScaledTickVector = new PointF( this.TickScale * x1 * (textSize.Height/2.0f), this.TickScale * y1 * (textSize.Height/2.0f) );

 					PointF rotatePoint;
 					if (this.TickTextNextToAxis) 
					{
 						rotatePoint = new PointF( tickStart.X - textScaledTickVector.X, tickStart.Y - textScaledTickVector.Y );
 					}
 					else 
					{
 						rotatePoint = new PointF( tickEnd.X + textScaledTickVector.X, tickEnd.Y + textScaledTickVector.Y );
 					}
 
 					float actualAngle;
					if (flipTicksLabel_) 
					{
 						double radAngle = (Math.PI / 180) * this.TicksLabelAngle;
 						rotatePoint.X += textSize.Width * (float)Math.Cos(radAngle);
 						rotatePoint.Y += textSize.Width * (float)Math.Sin(radAngle);
 						actualAngle = this.TicksLabelAngle + 180;
 					}
 					else 
					{
 						actualAngle = this.TicksLabelAngle;
					}
					

					g.TranslateTransform( rotatePoint.X, rotatePoint.Y );

					g.RotateTransform( actualAngle );
					
					Matrix m = g.Transform;
					PointF[] recPoints = new PointF[2];
					recPoints[0] = new PointF( 0.0f, -(textSize.Height / 2) );
					recPoints[1] = new PointF( textSize.Width, textSize.Height );
					m.TransformPoints( recPoints );

					float t_x1 = Math.Min( recPoints[0].X, recPoints[1].X );
					float t_x2 = Math.Max( recPoints[0].X, recPoints[1].X );
					float t_y1 = Math.Min( recPoints[0].Y, recPoints[1].Y );
					float t_y2 = Math.Max( recPoints[0].Y, recPoints[1].Y );
					
					boundingBox = Rectangle.Union(boundingBox, new Rectangle( (int)t_x1, (int)t_y1, (int)(t_x2-t_x1), (int)(t_y2-t_y1) ) );
					RectangleF drawRect = new RectangleF( 0.0f, -(textSize.Height / 2), textSize.Width, textSize.Height );

					g.DrawString( 
						text,
						tickTextFontScaled_,
						tickTextBrush_, 
						drawRect,
						drawFormat_ );

					t_x2 -= tickStart.X;
					t_y2 -= tickStart.Y;
					t_x2 *= 1.25f;
					t_y2 *= 1.25f;

					labelOffset = new Point( (int)t_x2, (int)t_y2 );

					g.ResetTransform();

					//g.DrawRectangle( new Pen(Color.Purple), boundingBox.X, boundingBox.Y, boundingBox.Width, boundingBox.Height );

				}
				else
				{

					float bx1 = (textCenterX - textSize.Width/2.0f);
					float by1 = (textCenterY - textSize.Height/2.0f);
					float bx2 = textSize.Width;
					float by2 = textSize.Height;

					RectangleF drawRect = new RectangleF( bx1, by1, bx2, by2 );
					Rectangle drawRect_int = new Rectangle( (int)bx1, (int)by1, (int)bx2, (int)by2 );
					// g.DrawRectangle( new Pen(Color.Green), bx1, by1, bx2, by2 );

					boundingBox = Rectangle.Union( boundingBox, drawRect_int );

					// g.DrawRectangle( new Pen(Color.Purple), boundingBox.X, boundingBox.Y, boundingBox.Width, boundingBox.Height );

					g.DrawString( 
						text,
						tickTextFontScaled_,
						tickTextBrush_,
						drawRect,
						drawFormat_ );

					textCenterX -= tickStart.X;
					textCenterY -= tickStart.Y;
					textCenterX *= 2.3f;
					textCenterY *= 2.3f;

					labelOffset = new Point( (int)textCenterX, (int)textCenterY );
				}
			} 

		}


		/// <summary>
		/// Draw the axis. This involves three steps:
		///  (1) Draw the axis line.
		///  (2) Draw the tick marks.
		///  (3) Draw the label.
		/// </summary>
		/// <param name="g">The drawing surface on which to draw.</param>
		/// <param name="physicalMin">The physical position corresponding to the world minimum of the axis.</param>
		/// <param name="physicalMax">The physical position corresponding to the world maximum of the axis.</param>
		/// <param name="boundingBox">out The bounding rectangle of the axis including axis line, label, tick marks and tick mark labels</param>
		public virtual void Draw( 
			System.Drawing.Graphics g,
			Point physicalMin,
			Point physicalMax, 
			out Rectangle boundingBox )
		{
			// calculate the bounds of the axis line only.
			int x1 = Math.Min( physicalMin.X, physicalMax.X );
			int x2 = Math.Max( physicalMin.X, physicalMax.X );
			int y1 = Math.Min( physicalMin.Y, physicalMax.Y );
			int y2 = Math.Max( physicalMin.Y, physicalMax.Y );
			Rectangle bounds = new Rectangle( x1, y1, x2-x1, y2-y1 );

			if (!Hidden)
			{
				
				// (1) Draw the axis line.
				g.DrawLine( this.linePen_, physicalMin.X, physicalMin.Y, physicalMax.X, physicalMax.Y );

				// (2) draw tick marks (subclass responsibility). 

				object labelOffset;
				object tickBounds;
				this.DrawTicks( g, physicalMin, physicalMax, out labelOffset, out tickBounds );

				// (3) draw the axis label
				object labelBounds = null;
				if (!this.HideTickText)
				{
					labelBounds = this.DrawLabel( g, (Point)labelOffset, physicalMin, physicalMax );
				}

				// (4) merge bounds and return.
				if (labelBounds != null)
					bounds = Rectangle.Union( bounds, (Rectangle)labelBounds );

				if (tickBounds != null)
					bounds = Rectangle.Union( bounds, (Rectangle)tickBounds );

			}

			boundingBox = bounds;
		}


		/// <summary>
		/// Update the bounding box and label offset associated with an axis
		/// to encompass the additionally specified mergeBoundingBox and 
		/// mergeLabelOffset respectively.
		/// </summary>
		/// <param name="labelOffset">Current axis label offset.</param>
		/// <param name="boundingBox">Current axis bounding box.</param>
		/// <param name="mergeLabelOffset">the label offset to merge. The current label offset will be replaced by this if it's norm is larger.</param>
		/// <param name="mergeBoundingBox">the bounding box to merge. The current bounding box will be replaced by this if null, or by the least upper bound of bother bounding boxes otherwise.</param>
		protected static void UpdateOffsetAndBounds( 
			ref object labelOffset, ref object boundingBox, 
			Point mergeLabelOffset, Rectangle mergeBoundingBox )
		{
			// determining largest label offset and use it.
			Point lo = (Point)labelOffset;
			double norm1 = Math.Sqrt( lo.X*lo.X + lo.Y*lo.Y );
			double norm2 = Math.Sqrt( mergeLabelOffset.X*mergeLabelOffset.X + mergeLabelOffset.Y*mergeLabelOffset.Y );
			if (norm1 < norm2)
			{
				labelOffset = mergeLabelOffset;
			}

			// determining bounding box.
			Rectangle b = mergeBoundingBox;
			if (boundingBox == null)
			{
				boundingBox = b;
			}
			else
			{
				boundingBox = Rectangle.Union( (Rectangle)boundingBox, b );
			}
		}


		/// <summary>
		/// DrawTicks method. In base axis class this does nothing.
		/// </summary>
		/// <param name="g">The graphics surface on which to draw</param>
		/// <param name="physicalMin">The physical position corresponding to the world minimum of the axis.</param>
		/// <param name="physicalMax">The physical position corresponding to the world maximum of the axis.</param>
		/// <param name="labelOffset">is set to a suitable offset from the axis to draw the axis label. In this base method, set to null.</param>
		/// <param name="boundingBox">is set to the smallest box that bounds the ticks and the tick text. In this base method, set to null.</param>
		protected virtual void DrawTicks( 
			Graphics g, 
			Point physicalMin, 
			Point physicalMax, 
			out object labelOffset,
			out object boundingBox )
		{
			labelOffset = null;
			boundingBox = null;
			// do nothing. This class is not abstract because a subclass may
			// want to override the Axis.Draw method to one that doesn't 
			// require DrawTicks.
		}



		/// <summary>
		/// World extent of the axis.
		/// </summary>
		public double WorldLength
		{
			get
			{
				return Math.Abs( worldMax_ - worldMin_ );
			}
		}

		/// <summary>
		/// Determines the positions, in world coordinates, of the large ticks. 
		/// When the physical extent of the axis is small, some of the positions 
		/// that were generated in this pass may be converted to small tick 
		/// positions and returned as well.
		/// 
		/// This default implementation returns empty large ticks list and null
		/// small tick list.
		/// </summary>
		/// <param name="physicalMin">The physical position corresponding to the world minimum of the axis.</param>
		/// <param name="physicalMax">The physical position corresponding to the world maximum of the axis.</param>
		/// <param name="largeTickPositions">ArrayList containing the positions of the large ticks.</param>
		/// <param name="smallTickPositions">ArrayList containing the positions of the small ticks if calculated, null otherwise.</param>
		internal virtual void WorldTickPositions_FirstPass(
			Point physicalMin, 
			Point physicalMax,
			out ArrayList largeTickPositions,
			out ArrayList smallTickPositions
			)
		{
			largeTickPositions = new ArrayList();
			smallTickPositions = null;
		}


		/// <summary>
		/// Determines the positions, in world coordinates, of the small ticks
		/// if they have not already been generated.
		/// 
		/// This default implementation creates an empty smallTickPositions list 
		/// if it doesn't already exist.
		/// </summary>
		/// <param name="physicalMin">The physical position corresponding to the world minimum of the axis.</param>
		/// <param name="physicalMax">The physical position corresponding to the world maximum of the axis.</param>
		/// <param name="largeTickPositions">The positions of the large ticks.</param>
		/// <param name="smallTickPositions">If null, small tick positions are returned via this parameter. Otherwise this function does nothing.</param>
		internal virtual void WorldTickPositions_SecondPass( 
			Point physicalMin,
			Point physicalMax,
			ArrayList largeTickPositions, 
			ref ArrayList smallTickPositions )
		{
			if (smallTickPositions == null)
				smallTickPositions = new ArrayList();
		}


		/// <summary>
		/// Determines the positions of all Large and Small ticks.
		/// </summary>
		/// <param name="physicalMin">The physical position corresponding to the world minimum of the axis.</param>
		/// <param name="physicalMax">The physical position corresponding to the world maximum of the axis.</param>
		/// <param name="largeTickPositions">ArrayList containing the positions of the large ticks.</param>
		/// <param name="smallTickPositions">ArrayList containing the positions of the small ticks.</param>
		public void WorldTickPositions(
			Point physicalMin,
			Point physicalMax,
			out ArrayList largeTickPositions,
			out ArrayList smallTickPositions
			)
		{
			WorldTickPositions_FirstPass( physicalMin, physicalMax, out largeTickPositions, out smallTickPositions );
			WorldTickPositions_SecondPass( physicalMin, physicalMax, largeTickPositions, ref smallTickPositions );
		}


		/// <summary>
		/// Moves the world min and max values so that the world axis
		/// length is [percent] bigger. If the current world
		/// max and min values are the same, they are moved appart 
		/// an arbitrary amount. This arbitrary amount is currently
		/// 0.01, and will probably be configurable in the future.
		/// </summary>
		/// <param name="percent">Percentage to increase world length by.</param>
		/// <remarks>Works for the case WorldMax is less than WorldMin.</remarks>
		public void IncreaseRange( double percent )
		{
			double range = WorldMax - WorldMin;
			
			if ( !Utils.DoubleEqual( range, 0.0 ) )
			{
				range *= percent;
			}
			else
			{
				// arbitrary number. 
				// TODO make this configurable.
				range = 0.01;
			}

			WorldMax += range;
			WorldMin -= range;
		}


		/// <summary>
		/// Scale label and tick fonts by this factor. Set by PlotSurface2D 
		/// Draw method.
		/// </summary>
		internal float FontScale 
		{
			get 
			{
				return fontScale_;
			}
 
			set 
			{
				fontScale_ = value;
				UpdateScale();
			}
		}
		private float fontScale_;


		/// <summary>
		/// Scale tick mark lengths by this factor. Set by PlotSurface2D
		/// Draw method.
		/// </summary>		
		internal float TickScale 
		{
			get 
			{
				return tickScale_;
			}
			set 
			{
				tickScale_ = value;
			}
		}
		private float tickScale_;


		private void UpdateScale()	
		{
			if (labelFont_ != null)
				this.labelFontScaled_ = Utils.ScaleFont( labelFont_, FontScale );
			
			if (tickTextFont_ != null)
				this.tickTextFontScaled_ = Utils.ScaleFont( tickTextFont_, FontScale );
		}


		/// <summary>
		/// Get whether or not this axis is linear.
		/// </summary>
		public virtual bool IsLinear
		{
			get
			{
				return true;
			}
		}

		private float labelOffset_ = 0;
		/// <summary>
		/// If LabelOffsetAbsolute is false (default) then this is the offset 
		/// added to default axis label position. If LabelOffsetAbsolute is 
		/// true, then this is the absolute offset of the label from the axis.
		/// 
		/// If positive, offset is further away from axis, if negative, towards
		/// the axis.
		/// </summary>
		public float LabelOffset
		{
			get
			{
				return labelOffset_;
			}
			set
			{
				labelOffset_ = value;
			}
		}

        private bool labelOffsetAbsolute_ = false;
        /// <summary>
        /// If true, the value specified by LabelOffset is the absolute distance
        /// away from the axis that the label is drawn. If false, the value 
        /// specified by LabelOffset is added to the pre-calculated value to 
        /// determine the axis label position.
        /// </summary>
        /// <value></value>
        public bool LabelOffsetAbsolute
        {
            get
            {
                return labelOffsetAbsolute_;
            }
            set
            {
                labelOffsetAbsolute_ = value;
            }
        }

        private bool labelOffsetScaled_ = true;
		/// <summary>
		/// Whether or not the supplied LabelOffset should be scaled by 
		/// a factor as specified by FontScale.
		/// </summary>
		public bool LabelOffsetScaled
		{
			get
			{
				return labelOffsetScaled_;
			}
			set
			{
				labelOffsetScaled_ = value;
			}
		}


		/// <summary>
		/// returns a suitable offset for the axis label in the case that there are no
		/// ticks or tick text in the way.
		/// </summary>
		/// <param name="physicalMin">physical point corresponding to the axis world maximum.</param>
		/// <param name="physicalMax">physical point corresponding to the axis world minimum.</param>
		/// <returns>axis label offset</returns>
		protected Point getDefaultLabelOffset( Point physicalMin, Point physicalMax )
		{
			System.Drawing.Rectangle tBoundingBox;
			System.Drawing.Point tLabelOffset;

			this.DrawTick( null, this.WorldMax, this.LargeTickSize, 
				"",
				new Point(0,0),
				physicalMin, physicalMax,
				out tLabelOffset, out tBoundingBox );

			return tLabelOffset;
		}


        /// <summary>
        /// Set the Axis color (sets all of axis line color, Tick text color, and label color).
        /// </summary>
        public Color Color
        {
            set
            {
                this.AxisColor = value;
                this.TickTextColor = value;
                this.LabelColor = value;
            }
        }


        /*

        // finish implementation of this at some point.

        public class WorldValueChangedArgs
        {
            public WorldValueChangedArgs( double value, MinMaxType minOrMax )
            {
                Value = value;
                MinOrMax = minOrMax;
            }

            public double Value;

            public enum MinMaxType
            {
                Min = 0,
                Max = 1
            }

            public MinMaxType MinOrMax;
        }


        public delegate void WorldValueChangedHandler( object sender, WorldValueChangedArgs e );

        public event WorldValueChangedHandler WorldMinChanged;
        public event WorldValueChangedHandler WorldMaxChanged;
        public event WorldValueChangedHandler WorldExtentsChanged;

        */

    }
}
